package api

import (
	"context"
	"net/http"
	"strings"

	"github.com/gorilla/mux"
	"github.com/ovh/cds/engine/api/database/gorpmapping"
	"github.com/ovh/cds/engine/api/keys"
	"github.com/ovh/cds/engine/api/project"
	"github.com/ovh/cds/engine/api/vcs"
	"github.com/ovh/cds/engine/service"
	"github.com/ovh/cds/sdk"
)

func (api *API) getKeysInProjectV2Handler() ([]service.RbacChecker, service.Handler) {
	return service.RBAC(api.projectRead),
		func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			vars := mux.Vars(r)
			key := vars["projectKey"]

			p, err := project.Load(ctx, api.mustDB(), key)
			if err != nil {
				return err
			}

			keys, err := project.LoadAllKeys(ctx, api.mustDB(), p.ID)
			if err != nil {
				return err
			}

			return service.WriteJSON(w, keys, http.StatusOK)
		}
}

func (api *API) deleteKeyInProjectV2Handler() ([]service.RbacChecker, service.Handler) {
	return service.RBAC(api.projectManage),
		func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			vars := mux.Vars(r)
			key := vars["projectKey"]
			keyName := vars["name"]

			u := getUserConsumer(ctx)
			if u == nil {
				return sdk.WithStack(sdk.ErrForbidden)
			}

			p, err := project.Load(ctx, api.mustDB(), key, project.LoadOptions.WithKeys)
			if err != nil {
				return sdk.WrapError(err, "cannot load project %s", key)
			}

			vcsProject, err := vcs.LoadAllVCSByProject(ctx, api.mustDB(), p.Key, gorpmapping.GetAllOptions.WithDecryption)
			if err != nil {
				return err
			}

			for _, v := range vcsProject {
				if v.Auth.SSHKeyName == keyName {
					return sdk.NewErrorFrom(sdk.ErrWrongRequest, "the ssh key is used by the vcs integration %s", v.Name)
				}
			}

			tx, err := api.mustDB().Begin()
			if err != nil {
				return sdk.WithStack(err)
			}
			defer tx.Rollback() // nolint
			for _, k := range p.Keys {
				if k.Name == keyName {
					if err := project.DeleteProjectKey(tx, p.ID, keyName); err != nil {
						return sdk.WrapError(err, "cannot delete key %s on project %s", k.Name, key)
					}
					break
				}
			}

			if err := tx.Commit(); err != nil {
				return sdk.WithStack(err)
			}

			return service.WriteJSON(w, nil, http.StatusOK)
		}
}

func (api *API) addKeyInProjectV2Handler() ([]service.RbacChecker, service.Handler) {
	return service.RBAC(api.projectManage),
		func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			vars := mux.Vars(r)
			key := vars["projectKey"]

			u := getUserConsumer(ctx)
			if u == nil {
				return sdk.WithStack(sdk.ErrForbidden)
			}

			var newKey sdk.ProjectKey
			if err := service.UnmarshalBody(r, &newKey); err != nil {
				return err
			}

			// check application name pattern
			regexp := sdk.NamePatternRegex
			if !regexp.MatchString(newKey.Name) {
				return sdk.WrapError(sdk.ErrInvalidKeyPattern, "key name %s do not respect pattern %s", newKey.Name, sdk.NamePattern)
			}

			p, errP := project.Load(ctx, api.mustDB(), key)
			if errP != nil {
				return sdk.WrapError(errP, "cannot load project %s", key)
			}
			newKey.ProjectID = p.ID

			if !strings.HasPrefix(newKey.Name, "proj-") {
				newKey.Name = "proj-" + newKey.Name
			}

			var k sdk.Key
			var err error
			switch newKey.Type {
			case sdk.KeyTypePGP:
				var email string
				email, err = api.gpgKeyEmailAddress(ctx, key, newKey.Name)
				if err != nil {
					return err
				}
				k, err = keys.GeneratePGPKeyPair(newKey.Name, "Project Key generated by CDS", email) // TODO email address
			case sdk.KeyTypeSSH:
				k, err = keys.GenerateSSHKey(newKey.Name)
			}
			if err != nil {
				return err
			}

			newKey.Private = k.Private
			newKey.Public = k.Public
			newKey.KeyID = k.KeyID

			tx, err := api.mustDB().Begin()
			if err != nil {
				return sdk.WithStack(err)
			}
			defer tx.Rollback() // nolint

			if err := project.InsertKey(tx, &newKey); err != nil {
				return err
			}

			if err := tx.Commit(); err != nil {
				return sdk.WithStack(err)
			}

			return service.WriteJSON(w, newKey, http.StatusOK)
		}
}

func (api *API) postDisableKeyInProjectV2Handler() ([]service.RbacChecker, service.Handler) {
	return service.RBAC(api.projectManage),
		func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			if !isAdmin(ctx) {
				return sdk.ErrForbidden
			}

			vars := mux.Vars(r)
			key := vars["projectKey"]
			keyName := vars["name"]

			p, err := project.Load(ctx, api.mustDB(), key, project.LoadOptions.WithKeys)
			if err != nil {
				return err
			}

			tx, err := api.mustDB().Begin()
			if err != nil {
				return sdk.WithStack(err)
			}
			defer tx.Rollback() // nolint

			var updateKey sdk.ProjectKey
			for _, k := range p.Keys {
				if k.Name == keyName {
					updateKey = k
					updateKey.Disabled = true
					if err := project.DisableProjectKey(tx, p.ID, keyName); err != nil {
						return err
					}
					break
				}
			}

			if err := tx.Commit(); err != nil {
				return err
			}

			return service.WriteJSON(w, nil, http.StatusOK)
		}
}

func (api *API) postEnableKeyInProjectV2Handler() ([]service.RbacChecker, service.Handler) {
	return service.RBAC(api.projectManage),
		func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			if !isAdmin(ctx) {
				return sdk.ErrForbidden
			}

			vars := mux.Vars(r)
			key := vars["projectKey"]
			keyName := vars["name"]

			p, err := project.Load(ctx, api.mustDB(), key, project.LoadOptions.WithKeys)
			if err != nil {
				return err
			}

			tx, err := api.mustDB().Begin()
			if err != nil {
				return sdk.WithStack(err)
			}
			defer tx.Rollback() // nolint

			var updateKey sdk.ProjectKey
			for _, k := range p.Keys {
				if k.Name == keyName {
					updateKey = k
					updateKey.Disabled = false
					if err := project.EnableProjectKey(tx, p.ID, keyName); err != nil {
						return err
					}
					break
				}
			}

			if err := tx.Commit(); err != nil {
				return sdk.WithStack(err)
			}

			return service.WriteJSON(w, nil, http.StatusOK)
		}
}
